﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Web.Caching;

namespace ASPNETPerformance.App_BusinessLogic.Providers {
    public class DiskCacheProvider : OutputCacheProvider {
        private string _cachePath;

        private string CachePath {
            get {
                if ( !string.IsNullOrEmpty( _cachePath ) )
                    return _cachePath;

                _cachePath = "~/Temp/";
                var context = HttpContext.Current;

                if ( context != null ) {
                    _cachePath = context.Server.MapPath( _cachePath );
                    if ( !_cachePath.EndsWith( "\\" ) )
                        _cachePath += "\\";
                }

                return _cachePath;
            }
        }

        public override object Add( string key, object entry, DateTime utcExpiry ) {
            Debug.WriteLine( "Cache.Add(" + key + ", " + entry + ", " + utcExpiry + ")" );

            var path = GetPathFromKey( key );

            if ( File.Exists( path ) )
                return entry;

            using ( var file = File.OpenWrite( path ) ) {
                var item = new CacheItem {
                    Expires = utcExpiry,
                    Item = entry
                };
                var formatter = new BinaryFormatter();
                formatter.Serialize( file, item );
            }

            return entry;
        }

        public override object Get( string key ) {
            Debug.WriteLine( "Cache.Get(" + key + ")" );

            var path = GetPathFromKey( key );

            if ( !File.Exists( path ) )
                return null;

            CacheItem item = null;

            using ( var file = File.OpenRead( path ) ) {
                var formatter = new BinaryFormatter();
                item = ( CacheItem )formatter.Deserialize( file );
            }

            if ( item == null || item.Expires <= DateTime.Now.ToUniversalTime() ) {
                Debug.WriteLine( "Expired: " + item.Expires + " <= " + DateTime.Now.ToUniversalTime() );
                Remove( key );
                return null;
            }

            return item.Item;
        }

        public override void Remove( string key ) {
            Debug.WriteLine( "Cache.Remove(" + key + ")" );

            var path = GetPathFromKey( key );
            if ( File.Exists( path ) )
                File.Delete( path );
        }

        public override void Set( string key, object entry, DateTime utcExpiry ) {
            Debug.WriteLine( "Cache.Set(" + key + ", " + entry + ", " + utcExpiry + ")" );

            var item = new CacheItem {
                Expires = utcExpiry,
                Item = entry
            };
            var path = GetPathFromKey( key );

            using ( var file = File.OpenWrite( path ) ) {
                var formatter = new BinaryFormatter();
                formatter.Serialize( file, item );
            }
        }

        private string GetPathFromKey( string key ) {
            return CachePath + MD5( key ) + ".txt";
        }

        private string MD5( string s ) {
            MD5CryptoServiceProvider provider;
            provider = new MD5CryptoServiceProvider();
            byte[] bytes = Encoding.UTF8.GetBytes( s );
            StringBuilder builder = new StringBuilder();

            bytes = provider.ComputeHash( bytes );

            foreach ( byte b in bytes )
                builder.Append( b.ToString( "x2" ).ToLower() );

            return builder.ToString();
        }

        [Serializable]
        internal class CacheItem {
            public DateTime Expires;
            public object Item;
        }
    }
}